﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace AgentTest
{
    // ApplicationMessage's are sent between wdad/dad and the collector.  These messages
    // are not specific to any Vegas channel, but are associated with the Vegas session.  See
    // VegasSession.SendApplicationMessage().

    public enum ApplicationMessageType : byte
    {
        SessionPing = 1,
        SessionPing2 = 2
    }

    public enum AgentState
    {
        Unknown = 0,
        Stopped = 1,
        Initializing = 2,
        DeterminingInstallation = 3,
        InstallationNotConfigured = 4,
        DeterminingAuditStore = 5,
        AgentNotInAuditStoreScope = 6,
        CollectorNotConnected = 7,
        SpoolPartitionFull = 8,
        Running = 9,
    }

    public class ApplicationMessage
    {
        public static ApplicationMessage CreateApplicationMessage(byte[] data)
        {
            MemoryReader memoryReader = new MemoryReader(data, 0, data.Length, true /*network order*/);
            ApplicationMessageType messageType = (ApplicationMessageType)memoryReader.GetByte();
            switch (messageType)
            {
                case ApplicationMessageType.SessionPing2:
                    return new SessionPingMessage2(memoryReader);
                case ApplicationMessageType.SessionPing:
                    return new SessionPingMessage(memoryReader);
                default:
                    throw new InvalidDataException(SafeFormat.Format("Unknown application message type: '{0}'", messageType));
            }
        }
    }

    public class SessionPingMessage : ApplicationMessage
    {
        public TimeSpan ExpirationInterval { get; private set; }
        public AgentState AgentState { get; private set; }
        public string AgentStateDescription { get; private set; }
        public DateTime StartupTime { get; private set; }

        public SessionPingMessage(TimeSpan expirationInterval, AgentState agentState, string agentStateDescription, DateTime startupTime)
        {
            if (agentStateDescription == null)
                throw new ArgumentNullException("agentStateDescription");
            ExpirationInterval = expirationInterval;
            AgentState = agentState;
            AgentStateDescription = agentStateDescription;
            StartupTime = startupTime;
        }

        internal SessionPingMessage(MemoryReader argumentReader)
        {
            ExpirationInterval = TimeSpan.FromSeconds(argumentReader.GetInt32());
            AgentState = (AgentState)argumentReader.GetInt32();
            AgentStateDescription = argumentReader.GetString();
            StartupTime = argumentReader.GetTimestamp();
        }

        public byte[] ToArray()
        {
            MemoryWriter argumentWriter = new MemoryWriter(true /*network order*/);
            argumentWriter.Put((byte)ApplicationMessageType.SessionPing);
            argumentWriter.Put((int)ExpirationInterval.TotalSeconds);
            argumentWriter.Put((int)AgentState);
            argumentWriter.Put(AgentStateDescription);
            argumentWriter.Put(StartupTime);
            return argumentWriter.ToArray();
        }

        public override string ToString()
        {
            return SafeFormat.Format("(ExpirationInterval = {0}, AgentState = {1}, AgentStateDescription = '{2}', StartupTime = {3})",
                                     (int)ExpirationInterval.TotalSeconds, AgentState, AgentStateDescription, StartupTime.ToLocalTime());
        }
    }

    public class SessionPingMessage2 : ApplicationMessage
    {
        public TimeSpan ExpirationInterval { get; private set; }
        public AgentState AgentState { get; private set; }
        public string AgentStateDescription { get; private set; }
        public DateTime StartupTime { get; private set; }
        public long UpTimeTicks { get; private set; }

        public SessionPingMessage2(TimeSpan expirationInterval, AgentState agentState, string agentStateDescription, DateTime startupTime, long upTimeTicks)
        {
            if (agentStateDescription == null)
                throw new ArgumentNullException("agentStateDescription");
            ExpirationInterval = expirationInterval;
            AgentState = agentState;
            AgentStateDescription = agentStateDescription;
            StartupTime = startupTime;
            UpTimeTicks = upTimeTicks;
        }

        internal SessionPingMessage2(MemoryReader argumentReader)
        {
            ExpirationInterval = TimeSpan.FromSeconds(argumentReader.GetInt32());
            AgentState = (AgentState)argumentReader.GetInt32();
            AgentStateDescription = argumentReader.GetString();
            StartupTime = argumentReader.GetTimestamp();
            UpTimeTicks = argumentReader.GetInt64();
        }

        public byte[] ToArray()
        {
            MemoryWriter argumentWriter = new MemoryWriter(true);
            argumentWriter.Put((byte)ApplicationMessageType.SessionPing2);
            argumentWriter.Put((int)ExpirationInterval.TotalSeconds);
            argumentWriter.Put((int)AgentState);
            argumentWriter.Put(AgentStateDescription);
            argumentWriter.Put(StartupTime);
            argumentWriter.Put(UpTimeTicks);
            return argumentWriter.ToArray();
        }

        public override string ToString()
        {
            return SafeFormat.Format("(ExpirationInterval = {0}, AgentState = {1}, AgentStateDescription = '{2}', StartupTime = {3}, UpTime = {4})",
                                     (int)ExpirationInterval.TotalSeconds, AgentState, AgentStateDescription, StartupTime.ToLocalTime(), TimeSpan.FromTicks(UpTimeTicks));
        }
    }

    public class SafeFormat
    {
        // Regex for looking for formatting indexes.
        static Regex m_regex = new Regex(@"\{(\d+)");

        // No public constructor.
        private SafeFormat() { }

        // Format a string, similar to string.Format(), but with additional
        // error handling.
        public static string Format(string format, params object[] args)
        {
            // If no arguments, return format string as-is.
            if (args == null || args.Length == 0)
                return format;

            StringBuilder formattedString = new StringBuilder();
            try
            {
                // Format the string.
                formattedString.AppendFormat(format, args);

                // Check for extra arguments.
                // First find highest argument referenced in the format string.
                int highestArgument = -1;
                StringBuilder formatWithoutLiteralBrace = new StringBuilder(format);
                formatWithoutLiteralBrace.Replace("{{", ""); // don't bother replacing }} escapes
                foreach (Match match in m_regex.Matches(formatWithoutLiteralBrace.ToString()))
                {
                    int formatIndex;
                    if (int.TryParse(match.Groups[1].Value, out formatIndex))
                        highestArgument = Math.Max(highestArgument, formatIndex);
                }

                // Append any extra arguments.
                if (args.Length > highestArgument + 1)
                {
                    formattedString.Append(", Extra information: ");
                    for (int extraIndex = highestArgument + 1; extraIndex < args.Length; extraIndex++)
                    {
                        if (extraIndex > highestArgument + 1)
                            formattedString.Append(", ");
                        formattedString.AppendFormat("{0}", args[extraIndex]);
                    }
                }
            }
            catch (Exception ex)
            {
                // Objects in the array can implement IFormattable or override ToString().
                // They can throw any exception, so we have to catch all here.

                // Rethrow termination exceptions.
                //CentrifyException.RethrowTerminationException(ex);

                // Try formatting with a simple format.
                formattedString = new StringBuilder();
                formattedString.AppendFormat("Formatting exception.  Format: '{0}', Args: ", format);
                for (int argumentIndex = 0; argumentIndex < args.Length; argumentIndex++)
                {
                    try
                    {
                        if (argumentIndex > 0)
                            formattedString.Append(", ");
                        formattedString.AppendFormat("'{0}'", args[argumentIndex].ToString());
                    }
                    catch (Exception ex2)
                    {
                        try
                        {
                            formattedString.AppendFormat("<ToString() Exception: {0}>", ex2);
                        }
                        catch (Exception)
                        {
                            formattedString.AppendFormat("<unable to log ToString() exception>");
                        }
                    }
                }
            }

            return formattedString.ToString();
        }
    }

    public class MemoryReader
    {
        MemoryStream m_stream;
        BinaryReader m_br;
        bool m_networkOrder;
        public MemoryReader(byte[] input, int off, int len, bool networkOrder)
        {
            m_stream = new MemoryStream(input, off, len);
            m_br = new BinaryReader(m_stream);
            m_networkOrder = networkOrder;
        }

        public int GetInt32()
        {
            int temp = m_br.ReadInt32();
            return m_networkOrder ? System.Net.IPAddress.NetworkToHostOrder(temp) : temp;
        }

        public short GetInt16()
        {
            short temp = m_br.ReadInt16();
            return m_networkOrder ? System.Net.IPAddress.NetworkToHostOrder(temp) : temp;
        }

        public string GetString()
        {
            int len = GetInt32();
            byte[] temp = m_br.ReadBytes(len);
            return System.Text.Encoding.UTF8.GetString(temp);
        }

        public byte[] GetBytes()
        {
            int len = GetInt32();
            return m_br.ReadBytes(len);
        }

        public byte GetByte()
        {
            return m_br.ReadByte();
        }

        public Guid GetGuid()
        {
            return new Guid(GetRawBytes(16));
        }

        public bool GetBool()
        {
            return GetByte() == 1;
        }

        public DateTime GetTimestamp()
        {
            // See http://support.microsoft.com/kb/167296
            int seconds = GetInt32();
            long win32FileTime = 10000000 * (long)seconds + 116444736000000000;
            DateTime timestamp = DateTime.FromFileTimeUtc(win32FileTime);
            int microseconds = GetInt32();
            timestamp = timestamp.AddTicks(microseconds * 10);
            return timestamp;
        }


        public byte[] GetCompressedBytes()
        {
            Int32 len = GetInt32();
            byte[] temp = m_br.ReadBytes(len);
            return temp;
        }

        public byte[] GetRawBytes(int len)
        {
            return m_br.ReadBytes(len);
        }

        public byte[] ToArray()
        {
            return m_stream.ToArray();
        }

        public byte[] GetRemainingBytes()
        {
            int numBytes = (int)(m_stream.Length - m_stream.Position);
            byte[] ret = new byte[numBytes];
            m_stream.Read(ret, 0, numBytes);
            return ret;
        }

        public long GetInt64()
        {
            long temp = m_br.ReadInt64();
            return m_networkOrder ? System.Net.IPAddress.NetworkToHostOrder(temp) : temp;

        }

        public byte[] GetBuffer()
        {
            return m_stream.GetBuffer();
        }

        public bool IsAtEnd()
        {
            return m_stream.Position >= m_stream.Length;
        }

        public void Seek(long offset, SeekOrigin location)
        {
            m_stream.Seek(offset, location);
        }

        public long Position
        {
            get { return m_stream.Position; }
        }

        public long Length
        {
            get { return m_stream.Length; }
        }
    }

    public class MemoryWriter
    {
        MemoryStream m_stream;
        BinaryWriter m_bw;
        long m_mark;
        bool m_networkOrder;

        public bool Empty
        {
            get { return (m_mark == m_stream.Length); }
        }
        public void Mark()
        {
            m_mark = m_stream.Length;
        }

        public MemoryWriter(byte[] buff, int off, int len, bool networkOrder)
        {
            m_stream = new MemoryStream(buff, off, len);
            m_bw = new BinaryWriter(m_stream);
            m_networkOrder = networkOrder;
        }
        public MemoryWriter(int len, bool networkOrder)
        {
            m_stream = new MemoryStream(len);
            m_bw = new BinaryWriter(m_stream);
            m_networkOrder = networkOrder;
        }
        public MemoryWriter(bool networkOrder)
        {
            m_stream = new MemoryStream();
            m_bw = new BinaryWriter(m_stream);
            m_networkOrder = networkOrder;
        }

        public void Put(int val)
        {
            if (m_networkOrder)
                val = System.Net.IPAddress.HostToNetworkOrder(val);
            m_bw.Write(val);
        }

        public void PutRaw(int val)
        {
            m_bw.Write(val);
        }

        public void Put(short val)
        {
            if (m_networkOrder)
                val = System.Net.IPAddress.HostToNetworkOrder(val);
            m_bw.Write(val);
        }

        public void PutRaw(short val)
        {
            m_bw.Write(val);
        }

        public void Put(string val)
        {
            byte[] temp = System.Text.Encoding.UTF8.GetBytes(val);
            Put(temp.Length);
            m_bw.Write(temp);
        }

        public void Put(bool val)
        {
            byte v = (byte)(val ? 1 : 0);
            m_bw.Write(v);
        }

        public void Put(byte val)
        {
            m_bw.Write(val);
        }

        public void Put(byte[] val)
        {
            Put(val.Length);
            m_bw.Write(val);
        }

        public void Put(Guid guid)
        {
            PutRaw(guid.ToByteArray());
        }

        public void Put(DateTime timestamp)
        {
            // See http://support.microsoft.com/kb/167296
            long win32FileTime = timestamp.ToFileTimeUtc();
            long totalMicroseconds = (win32FileTime - 116444736000000000) / 10;
            int seconds = (int)(totalMicroseconds / 1000000);
            int microseconds = (int)(totalMicroseconds % 1000000);
            Put(seconds);
            Put(microseconds);
        }

        public int PutCompressedBytes(byte[] val)
        {
            // Compressed byte arrays are stored as:
            // | Int32 - Compressed Length | [Length bytes] |
            // If a consumer needs to know the uncompressed size
            //  he needs to write it somewhere 

            // need to save our current location
            //  as we'll need to come back here and 
            //  poke the compressed buffer length in            
            long startPosition = m_stream.Position;
            Put((Int32)0);   // Placeholder
            // Length of stream before we add compressed buffer
            int startLength = (int)m_stream.Length;

            // Create a gzip stream and write the given byte array            
            //DeflateStream gzs = new DeflateStream(m_stream, CompressionMode.Compress, true);
            ////GZipStream gzs = new GZipStream(m_stream, CompressionMode.Compress, true);
            //gzs.Write(val, 0, val.Length);
            //gzs.Flush();
            //gzs.Close();

            // Now go back and poke our resultant length in
            Int32 compressedLen = (int)m_stream.Length - startLength;
            long savePos = m_stream.Position;
            m_stream.Position = startPosition;
            Put(compressedLen);
            m_stream.Position = savePos;

            // return the compressed size, in case the consumer cares
            return compressedLen;
        }

        public void PutRaw(byte[] val)
        {
            m_bw.Write(val);
        }

        public byte[] ToArray()
        {
            return m_stream.ToArray();
        }

        public byte[] GetBuffer()
        {
            return m_stream.GetBuffer();
        }

        public long Length
        {
            get { return m_stream.Length; }
        }

        public void Put(long val)
        {
            if (m_networkOrder)
                val = System.Net.IPAddress.HostToNetworkOrder(val);
            m_bw.Write(val);
        }

        public void PutRaw(long val)
        {
            m_bw.Write(val);
        }


    }
}
